﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Caching;
using ChannelVN.CMS.Common;
using System.Runtime.Serialization;
using Enyim.Caching;
using ChannelVN.Cached.Config;
using Enyim.Caching.Memcached;

namespace ChannelVN.Cached.CacheClients
{
    [Serializable]
    public class MemcachedKeyGroups : ISerializable
    {
        private readonly IDictionary<string, MemcachedKeys> _groups;

        private static MemcachedClient _systemMemcachedClient;

        private const int TimeIntervalForUpdateExpiredKeys = 1; // in hours
        private static DateTime _lastUpdateExpiredKeys = DateTime.Now;

        private const int CacheExpiredForKeyGroups = 60 * 24 * 10; // in minute (10 days)
        private const string CacheNameForKeyGroups = "StorageForMemcachedKeys";

        public static MemcachedKeyGroups Current
        {
            get
            {
                try
                {
                    if (null == _systemMemcachedClient)
                        _systemMemcachedClient = new MemcachedClient("enyim.com/" + AppConfigs.DefaultMemcachedSectionName);

                    return _systemMemcachedClient.Get<MemcachedKeyGroups>(CacheNameForKeyGroups) ??
                           new MemcachedKeyGroups();
                }
                catch (Exception)
                {
                    return new MemcachedKeyGroups();
                }
            }
        }

        public MemcachedKeys this[string groupName]
        {
            get
            {
                return ContaintsGroup(groupName) ? _groups[groupName] : null;
            }
        }
        public MemcachedKey GetMemcachedKeyInfo(string groupName, string cachedKey)
        {
            if (ContaintsGroup(groupName))
            {
                var keyList = _groups[groupName];
                if (null != keyList)
                {
                    return keyList[cachedKey];
                }
            }
            return null;
        }
        public string UpdateKeyInfo(string groupName, string cachedKey, DateTime lastUpdate, DateTime cachedExpired)
        {
            if (ContaintsGroup(groupName))
            {
                var keyList = _groups[groupName];
                if (null != keyList)
                {
                    keyList.Update(cachedKey, lastUpdate, cachedExpired);
                }
            }
            else
            {
                var newKeyList = new MemcachedKeys(groupName);
                newKeyList.Update(cachedKey, lastUpdate, cachedExpired);
                _groups.Add(groupName, newKeyList);
            }
            SaveAllKeyGroup();
            return _groups[groupName].GetPublishedKey(cachedKey);
        }
        public bool ContaintsGroup(string groupName)
        {
            return !string.IsNullOrEmpty(groupName) && _groups.ContainsKey(groupName);
        }

        public bool ContaintsKey(string groupName, string cachedKey)
        {
            if (string.IsNullOrEmpty(groupName) || string.IsNullOrEmpty(cachedKey)) return false;
            if (ContaintsGroup(groupName))
            {
                var keyList = _groups[groupName];
                if (null != keyList)
                {
                    return keyList.ContainsKey(cachedKey);
                }
            }
            return false;
        }
        public void RemoveKey(string groupName, string cachedKey)
        {
            if (!ContaintsGroup(groupName) || string.IsNullOrEmpty(cachedKey)) return;

            var keyList = _groups[groupName];

            if (null == keyList) return;

            keyList.Remove(cachedKey);
            SaveAllKeyGroup();
        }
        public void RemoveGroup(string groupName)
        {
            _groups.Remove(groupName);
            SaveAllKeyGroup();
        }
        public int GetKeyCountInGroup(string groupName)
        {
            return ContaintsGroup(groupName) ? (null != this[groupName] ? this[groupName].GetKeyCount() : 0) : 0;
        }
        public int GetGroupCount()
        {
            return _groups.Count;
        }
        private void SaveAllKeyGroup()
        {
            if (null == _systemMemcachedClient)
                _systemMemcachedClient = new MemcachedClient("enyim.com/" + AppConfigs.DefaultMemcachedSectionName);

            if (_lastUpdateExpiredKeys <= DateTime.Now)
            {
                RemoveExpiredKeys();
                _lastUpdateExpiredKeys = _lastUpdateExpiredKeys.AddHours(TimeIntervalForUpdateExpiredKeys);
            }

            _systemMemcachedClient.Remove(CacheNameForKeyGroups);
            _systemMemcachedClient.Store(StoreMode.Add, CacheNameForKeyGroups, this, DateTime.Now.AddMinutes(CacheExpiredForKeyGroups));
        }
        private void RemoveExpiredKeys()
        {
            var groups = _groups.Keys.ToList();
            foreach (var group in groups)
            {
                var keys = _groups[group];
                if (keys.RemoveExpiredKeys() > 0)
                {
                    _groups[group] = keys;
                }
            }
        }
        public override string ToString()
        {
            return NewtonJson.Serialize(this);
        }

        public MemcachedKeyGroups()
        {
            _groups = new Dictionary<string, MemcachedKeys>();
        }
        public MemcachedKeyGroups(SerializationInfo info, StreamingContext ctxt)
        {
            _groups = (IDictionary<string, MemcachedKeys>)info.GetValue("_gs", typeof(IDictionary<string, MemcachedKeys>));
        }
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("_gs", _groups);
        }
    }
    [Serializable]
    public class MemcachedKeys : ISerializable
    {
        private readonly IDictionary<string, MemcachedKey> _keys;
        private readonly string _group;

        public int GetKeyCount()
        {
            return _keys.Count;
        }

        public MemcachedKey this[string key]
        {
            get { return _keys[key]; }
        }
        private void Update(string key, MemcachedKey keyInfo)
        {
            if (_keys.ContainsKey(key))
            {
                _keys[key] = keyInfo;
            }
            else
            {
                _keys.Add(key, keyInfo);
            }
        }
        public void Update(string key, DateTime lastUpdated, DateTime cachedExpired)
        {
            Update(key, new MemcachedKey() { LastUpdate = lastUpdated, Expired = cachedExpired });
        }
        public void Remove(string key)
        {
            _keys.Remove(key);
        }
        public bool ContainsKey(string key)
        {
            if (string.IsNullOrEmpty(key)) return false;
            return _keys.ContainsKey(key);
        }
        public ICollection<string> GetAllKeys()
        {
            return _keys.Keys;
        }
        public string GetPublishedKey(string key)
        {
            return _group + key;
        }
        public int RemoveExpiredKeys()
        {
            var count = 0;
            var keys = GetAllKeys().ToList();

            foreach (var key in keys.Where(key => ContainsKey(key) && this[key].Expired < DateTime.Now))
            {
                Remove(key);
                count++;
            }
            return count;
        }

        public MemcachedKeys(string groupName)
        {
            _keys = new Dictionary<string, MemcachedKey>();
            _group = groupName;
        }
        public MemcachedKeys(SerializationInfo info, StreamingContext ctxt)
        {
            _keys = (IDictionary<string, MemcachedKey>)info.GetValue("_ks", typeof(IDictionary<string, MemcachedKey>));
            _group = (string)info.GetValue("_g", typeof(string));
        }
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("_ks", _keys);
            info.AddValue("_g", _group);
        }
    }
    [Serializable]
    public class MemcachedKey : ISerializable
    {
        //public string PublishedKey { get; set; }
        public DateTime LastUpdate { get; set; }
        public DateTime Expired { get; set; }

        //public MemcachedKey(string publishedKey)
        public MemcachedKey()
        {
            //PublishedKey = publishedKey;
            LastUpdate = DateTime.Now;
            Expired = DateTime.Now;
        }

        public MemcachedKey(SerializationInfo info, StreamingContext ctxt)
        {
            //PublishedKey = (string)info.GetValue("PublishedKey", typeof(string));
            LastUpdate = (DateTime)info.GetValue("_u", typeof(DateTime));
            Expired = (DateTime)info.GetValue("_e", typeof(DateTime));
        }
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            //info.AddValue("PublishedKey", PublishedKey);
            info.AddValue("_u", LastUpdate);
            info.AddValue("_e", Expired);
        }
    }
}
